#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/5/20 19:43
# @Author : StableGenius
# @Site : https://www.zhihu.com/people/im401
# @File : check_paper_with_baidu.py
# @Software: PyCharm
# @Description: Check paper status

import re
import os
import base64
import logging
import time
from urllib.parse import urlparse

import requests
# 需要在系统当中安装nodejs，否则无法使用execjs
import execjs
from fake_useragent import UserAgent

logging.basicConfig(level=logging.INFO,
                    format="%(asctime)s %(name)s %(levelname)s %(message)s",
                    datefmt='%Y-%m-%d  %H:%M:%S %a')


class BaiduAI:
    """
    使用这个类处理所有与百度AI开放平台相关的操作
    """
    def __init__(self, api_key: str, secret_key: str):
        """
        使用在网页上拿到的API Key和Secret Key完成百度AI的认证
        :param api_key: API Key
        :param secret_key: Secret Key
        """
        self.ak = api_key
        self.sk = secret_key

    def get_token(self) -> str:
        """
        从百度AI处获得使用OCR权限
        :return: 从鉴权中心拿到的token
        """
        host = f'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={self.ak}&client_secret={self.sk}'
        return requests.get(host).json()['access_token']

    def ocr(self, img: str) -> str:
        """
        使用百度AI完成验证码识别
        :param img: 图片的二进制流
        :return: 识别结果
        """
        request_url = "https://aip.baidubce.com/rest/2.0/ocr/v1/numbers"
        img = base64.b64encode(img)

        params = {"image": img}
        access_token = self.get_token()
        request_url = request_url + "?access_token=" + access_token
        headers = {'content-type': 'application/x-www-form-urlencoded'}
        resp = requests.post(request_url, data=params, headers=headers).json()
        return resp['words_result'][0]['words']


class Paper:
    """
    使用Paper类处理所有与论文状态相关的所有操作
    """
    def __init__(self, login_url: str, post_url: str, captcha_url: str, status_url: str):
        """
        根据投稿系统的相关信息进行配置
        :param login_url: 投稿系统的登录网址
        :param post_url: 登录系统时发送POST请求的目标网址
        :param captcha_url: 获取验证码的目标网址
        :param status_url: 查询稿件状态的目标网址
        """
        self.login_url = login_url
        self.post_url = post_url
        self.captcha_url = captcha_url
        self.status_url = status_url

        # 使用session管理登录状态，放一个占位符防止Pycharm亮警告
        self.session = None

        # 切割登录链接，填充headers
        self.host = urlparse(self.login_url).netloc
        self.headers = {
            'Host': self.host,
            'User-Agent': str(UserAgent().random),
        }

        # 读取加密用的js代码
        file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "encdec.js")
        with open(file_path) as f:
            self.js = execjs.compile(f.read())

        # 登录时提交的表单内容
        self.form_data = {}

    def pre_login(self, username: str, password: str) -> None:
        """
        登录前的准备，加密用户明和密码，更新cookies
        :param username: 用户名
        :param password: 密码
        :return:
        """
        self.session = requests.Session()

        # 打开登录页面，获取cookies
        self.session.get(url=self.login_url, headers=self.headers)

        # 根据用户名和密码，计算出必要的加密字符串
        self.form_data = {
            'j_username': self.js.call("des", username, "ech", "gt", "ma") + '___1___author___jaiimjlsuan',
            'j_password': self.js.call("des", password, "ech", "gt", "ma"),
        }

        # 更新session的cookies
        self.session.cookies.set('author_password_id', '', domain=self.host)
        self.session.cookies.set('author_user_id', self.js.call('enc_cookie', username), domain=self.host)

    def get_captcha(self) -> str:
        """
        获取一张验证码图片
        :return: 返回验证码图片的二进制流
        """
        # 获取验证码图片
        return self.session.get(url=self.captcha_url, headers=self.headers).content

    def login(self, captcha_code: str) -> bool:
        """
        尝试带上验证码登录网站
        @param captcha_code: 验证码
        @return: True代表成功登录，False代表登录失败
        """
        # 将验证码写入form-data
        self.form_data.update({'j_randomCode': captcha_code})

        headers = self.headers.copy()
        headers.update({
            'Content-Type': 'application/x-www-form-urlencoded',
            'Origin': 'http://tjyj.stats.gov.cn',
            'Referer': 'http://tjyj.stats.gov.cn/JournalX_tjyj/authorLogOn.action',
        })

        before_cookies = self.session.cookies.values()
        # 向服务器发送POST请求，模拟登录
        self.session.post(url=self.post_url,
                          headers=headers,
                          data=self.form_data)

        for value in self.session.cookies.values():
            # 只有session的cookies发生了变化才算登录成功
            if value not in before_cookies:
                return True

        # cookie没有发生变化就是登陆失败，包括了密码错误，验证码错误，500错误等等所有情况
        return False

    def check_status(self) -> bool:
        """
        查阅稿件状态
        """
        resp = self.session.get(url=self.status_url, headers=self.headers).text
        if status := re.search(r'状态.*?([\u4e00-\u9fa5]+)', resp, re.S).group(1):
            logging.info(f"当前稿件状态为 {status} !")
            return status
        else:
            logging.info("稿件状态查询失败！")
            return False


def sleep():
    logging.info('上一步骤失败了，休息3秒以后重新尝试！')
    time.sleep(3)


def run(config_dict: dict) -> str:
    """
    程序入口
    :param config_dict: 填入配置字典
    :return:
    """
    logging.info('正在配置杂志社相关信息...')
    paper = Paper(login_url=config_dict['login_url'],
                  post_url=config_dict['post_url'],
                  captcha_url=config_dict['captcha_url'],
                  status_url=config_dict['status_url'])

    while True:
        logging.info('正在配置用户名与密码...')
        paper.pre_login(config_dict['username'], config_dict['password'])

        logging.info('正在获取验证码图片...')
        img = paper.get_captcha()

        # 配置百度AI相关信息
        baidu = BaiduAI(config_dict['baidu_ak'], config_dict['baidu_sk'])

        logging.info('正在使用百度AI识别图片验证码...')
        try:
            captcha = baidu.ocr(img)
            logging.info(f'验证码识别结果为 {captcha}')
            if len(captcha) != 5:
                raise KeyError
        except KeyError:
            logging.info('未能成功识别验证码，此次尝试登录失败！')
            sleep()
            continue

        logging.info('正在尝试带验证码登录...')
        if not paper.login(captcha):
            sleep()
            continue

        logging.info('正在尝试读取稿件状态')
        if x := paper.check_status():
            return x
        else:
            sleep()


if __name__ == '__main__':
    # 配置相关信息
    config = {
        'login_url': "http://tjyj.stats.gov.cn/JournalX_tjyj/authorLogOn.action",
        'post_url': "http://tjyj.stats.gov.cn/JournalX_tjyj/j_acegi_security_check",
        'captcha_url': 'http://tjyj.stats.gov.cn/JournalX_tjyj/kaptcha.jpg',
        "status_url": "http://tjyj.stats.gov.cn/JournalX_tjyj/author/Author!disposing.action",

        "username": '',
        'password': '',

        'baidu_ak': '',
        'baidu_sk': '',
    }

    # 根据配置信息，启动程序
    print(run(config_dict=config))
